home *** CD-ROM | disk | FTP | other *** search
/ Java Developer's Companion / Java Developer's Companion.iso / documentation / tutorial / example / Animator.java < prev    next >
Encoding:
Java Source  |  1997-07-13  |  23.3 KB  |  949 lines

  1. /*
  2.  * Copyright (c) 1995-1997 Sun Microsystems, Inc. All Rights Reserved.
  3.  *
  4.  * Permission to use, copy, modify, and distribute this software
  5.  * and its documentation for NON-COMMERCIAL purposes and without
  6.  * fee is hereby granted provided that this copyright notice
  7.  * appears in all copies. Please refer to the file "copyright.html"
  8.  * for further important copyright and licensing information.
  9.  *
  10.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  11.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  12.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  13.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  14.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  15.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  16.  */
  17. /*
  18.  * @(#)Animator.java    1.5 95/11/29 Herb Jellinek
  19.  *
  20.  * Copyright (c) 1994-1995 Sun Microsystems, Inc. All Rights Reserved.
  21.  *
  22.  * Permission to use, copy, modify, and distribute this software
  23.  * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
  24.  * without fee is hereby granted.
  25.  * Please refer to the file http://java.sun.com/copy_trademarks.html
  26.  * for further important copyright and trademark information and to
  27.  * http://java.sun.com/licensing.html for further important licensing
  28.  * information for the Java (tm) Technology.
  29.  *
  30.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  31.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  32.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  33.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  34.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  35.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  36.  *
  37.  * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
  38.  * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
  39.  * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
  40.  * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
  41.  * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
  42.  * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
  43.  * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES").  SUN
  44.  * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
  45.  * HIGH RISK ACTIVITIES.
  46.  */
  47.  
  48. import java.io.InputStream;
  49. import java.awt.*;
  50. import java.awt.image.ImageProducer;
  51. import java.applet.Applet;
  52. import java.applet.AudioClip;
  53. import java.util.Vector;
  54. import java.util.Hashtable;
  55. import java.util.Enumeration;
  56. import java.net.URL;
  57. import java.net.MalformedURLException;
  58.  
  59. /**
  60.  * An applet that plays a sequence of images, as a loop or a one-shot.
  61.  * Can have a soundtrack and/or sound effects tied to individual frames.
  62.  *
  63.  * @author Herb Jellinek
  64.  * @version 1.5, 29 Nov 1995
  65.  */
  66.  
  67. public class Animator extends Applet implements Runnable {
  68.     
  69.     /**
  70.      * The images, in display order (Images).
  71.      */
  72.     Vector images = null;
  73.  
  74.     /**
  75.      * Duration of each image (Integers, in milliseconds).
  76.      */
  77.     Hashtable durations = null;
  78.  
  79.     /**
  80.      * Sound effects for each image (AudioClips).
  81.      */
  82.     Hashtable sounds = null;
  83.  
  84.     /**
  85.      * Position of each image (Points).
  86.      */
  87.     Hashtable positions = null;
  88.  
  89.     /**
  90.      * MediaTracker 'class' ID numbers.
  91.      */
  92.  
  93.     static final int STARTUP_ID    = 0;
  94.     static final int BACKGROUND_ID = 1;
  95.     static final int ANIMATION_ID  = 2;
  96.  
  97.     /**
  98.      * Start-up image URL, if any.
  99.      */
  100.     URL startUpImageURL = null;
  101.  
  102.     /**
  103.      * Start-up image, if any.
  104.      */
  105.     Image startUpImage = null;
  106.  
  107.     /**
  108.      * Background image URL, if any.
  109.      */
  110.     URL backgroundImageURL = null;
  111.  
  112.     /**
  113.      * Background image, if any.
  114.      */
  115.     Image backgroundImage = null;
  116.  
  117.     /**
  118.      * The soundtrack's URL.
  119.      */
  120.     URL soundtrackURL = null;
  121.  
  122.     /**
  123.      * The soundtrack.
  124.      */
  125.     AudioClip soundtrack;
  126.  
  127.     /**
  128.      * Largest width.
  129.      */
  130.     int maxWidth = 0;
  131.  
  132.     /**
  133.      * Largest height.
  134.      */
  135.     int maxHeight = 0;
  136.  
  137.     /**
  138.      * Was there a problem loading the current image?
  139.      */
  140.     boolean imageLoadError = false;
  141.  
  142.     /**
  143.      * The directory or URL from which the images are loaded
  144.      */
  145.     URL imageSource = null;
  146.  
  147.     /**
  148.      * The directory or URL from which the sounds are loaded
  149.      */
  150.     URL soundSource = null;
  151.  
  152.     /**
  153.      * The thread animating the images.
  154.      */
  155.     Thread engine = null;
  156.  
  157.     /**
  158.      * The current loop slot - index into 'images.'
  159.      */
  160.     int frameNum;
  161.  
  162.     /**
  163.      * frameNum as an Object - suitable for use as a Hashtable key.
  164.      */
  165.     Integer frameNumKey;
  166.     
  167.     /**
  168.      * The current X position (for painting).
  169.      */
  170.     int xPos = 0;
  171.     
  172.     /**
  173.      * The current Y position (for painting).
  174.      */
  175.     int yPos = 0;
  176.     
  177.     /**
  178.      * The default number of milliseconds to wait between frames.
  179.      */
  180.     public static final int defaultPause = 3900;
  181.     
  182.     /**
  183.      * The global delay between images, which can be overridden by
  184.      * the PAUSE parameter.
  185.      */
  186.     int globalPause = defaultPause;
  187.  
  188.     /**
  189.      * Whether or not the thread has been paused by the user.
  190.      */
  191.     boolean userPause = false;
  192.  
  193.     /**
  194.      * Repeat the animation?  If false, just play it once.
  195.      */
  196.     boolean repeat;
  197.  
  198.     /**
  199.      * The offscreen image, used in double buffering
  200.      */
  201.     Image offScrImage;
  202.  
  203.     /**
  204.      * The offscreen graphics context, used in double buffering
  205.      */
  206.     Graphics offScrGC;
  207.  
  208.     /**
  209.      * The MediaTracker we use to load our images.
  210.      */
  211.     MediaTracker tracker;
  212.     
  213.     /**
  214.      * Can we paint yet?
  215.      */
  216.     boolean loaded = false;
  217.  
  218.     /**
  219.      * Was there an initialization error?
  220.      */
  221.     boolean error = false;
  222.  
  223.     /**
  224.      * What we call an image file in messages.
  225.      */
  226.     final static String imageLabel = "image";
  227.     
  228.     /**
  229.      * What we call a sound file in messages.
  230.      */
  231.     final static String soundLabel = "sound";
  232.     
  233.     /**
  234.      * Print silly debugging info?
  235.      */
  236.     final boolean debug = false;
  237.  
  238.     /**
  239.      * Applet info.
  240.      */
  241.     public String getAppletInfo() {
  242.     return "Animator v1.5, by Herb Jellinek";
  243.     }
  244.  
  245.     /**
  246.      * Parameter info.
  247.      */
  248.     public String[][] getParameterInfo() {
  249.     String[][] info = {
  250.         {"imagesource",     "URL",         "a directory"},
  251.         {"startup",     "URL",         "displayed at startup"},
  252.         {"background",     "URL",         "displayed as background"},
  253.         {"startimage",     "int",         "start index"},
  254.         {"endimage",     "int",         "end index"},
  255.         {"namepattern",     "URL",          "used to generate indexed names"},
  256.         {"pause",             "int",         "milliseconds"},
  257.         {"pauses",             "ints",     "milliseconds"},
  258.         {"repeat",             "boolean",     "repeat or not"},
  259.         {"positions",    "coordinates",     "path"},
  260.         {"soundsource",    "URL",         "audio directory"},
  261.         {"soundtrack",    "URL",         "background music"},
  262.         {"sounds",        "URLs",        "audio samples"},
  263.     };
  264.     return info;
  265.     }
  266.  
  267.     /**
  268.      * Print silly debugging info.
  269.      */
  270.     void dbg(String s) {
  271.     if (debug) {
  272.         System.out.println("> "+s);
  273.     }
  274.     }
  275.  
  276.     /**
  277.      * Local version of getParameter for debugging purposes.
  278.      */
  279.     public String getParameter(String key) {
  280.     String result = super.getParameter(key);
  281.     dbg("getParameter("+key+") = "+result);
  282.     return result;
  283.     }
  284.  
  285.     final int setFrameNum(int newFrameNum) {
  286.     frameNumKey = new Integer(frameNum = newFrameNum);
  287.     return frameNum;
  288.     }
  289.     
  290.     void updateMaxDims(Dimension dim) {
  291.     maxWidth = Math.max(dim.width, maxWidth);
  292.     maxHeight = Math.max(dim.height, maxHeight);
  293.     dbg("New width = "+maxWidth+", height = "+maxHeight);
  294.     }
  295.  
  296.     /**
  297.      * Parse the IMAGES parameter.  It looks like
  298.      * 1|2|3|4|5, etc., where each number (item) names a source image.
  299.      *
  300.      * @return a Vector of (URL) image file names.
  301.      */
  302.     Vector parseImages(String attr)
  303.     throws MalformedURLException {
  304.     Vector result = new Vector(10);
  305.     for (int i = 0; i < attr.length(); ) {
  306.         int next = attr.indexOf('|', i);
  307.         if (next == -1) next = attr.length();
  308.         String file = attr.substring(i, next);
  309.         result.addElement(new URL(imageSource, "T"+file+".gif"));
  310.         i = next + 1;
  311.     }
  312.     return result;
  313.     }
  314.  
  315.     /**
  316.      * Fetch the images named in the argument, updating 
  317.      * maxWidth and maxHeight as we go.
  318.      * Is restartable.
  319.      *
  320.      * @param images a Vector of URLs
  321.      * @return true if all went well, false otherwise.
  322.      */
  323.     boolean fetchImages(Vector images) {
  324.     int i;
  325.     int size = images.size();
  326.     for (i = 0; i < size; i++) {
  327.         Object o = images.elementAt(i);
  328.         if (o instanceof URL) {
  329.         URL url = (URL)o;
  330.         tellLoadingMsg(url, imageLabel);
  331.         Image im = getImage(url);
  332.         tracker.addImage(im, ANIMATION_ID);
  333.         images.setElementAt(im, i);
  334.         }
  335.     }
  336.  
  337.     try {
  338.         tracker.waitForID(ANIMATION_ID);
  339.     } catch (InterruptedException e) {}
  340.     if (tracker.isErrorID(ANIMATION_ID)) {
  341.         return false;
  342.     }
  343.     
  344.     for (i = 0; i < size; i++) {
  345.         updateMaxDims(getImageDimensions((Image)images.elementAt(i)));
  346.     }
  347.  
  348.     return true;
  349.     }
  350.  
  351.     /**
  352.      * Parse the SOUNDS parameter.  It looks like
  353.      * train.au||hello.au||stop.au, etc., where each item refers to a
  354.      * source image.  Empty items mean that the corresponding image
  355.      * has no associated sound.
  356.      *
  357.      * @return a Hashtable of SoundClips keyed to Integer frame numbers.
  358.      */
  359.     Hashtable parseSounds(String attr, Vector images)
  360.     throws MalformedURLException {
  361.     Hashtable result = new Hashtable();
  362.  
  363.     int imageNum = 0;
  364.     int numImages = images.size();
  365.     for (int i = 0; i < attr.length(); ) {
  366.         if (imageNum >= numImages) break;
  367.         
  368.         int next = attr.indexOf('|', i);
  369.         if (next == -1) next = attr.length();
  370.         
  371.         String sound = attr.substring(i, next);
  372.         if (sound.length() != 0) {
  373.         result.put(new Integer(imageNum),
  374.                new URL(soundSource, sound));
  375.         }
  376.         i = next + 1;
  377.         imageNum++;
  378.     }
  379.  
  380.     return result;
  381.     }
  382.  
  383.     /**
  384.      * Fetch the sounds named in the argument.
  385.      * Is restartable.
  386.      *
  387.      * @return URL of the first bogus file we hit, null if OK.
  388.      */
  389.     URL fetchSounds(Hashtable sounds) {
  390.     for (Enumeration e = sounds.keys() ; e.hasMoreElements() ;) {
  391.         Integer num = (Integer)e.nextElement();
  392.         Object o = sounds.get(num);
  393.         if (o instanceof URL) {
  394.         URL file = (URL)o;
  395.         tellLoadingMsg(file, soundLabel);
  396.         try {
  397.             sounds.put(num, getAudioClip(file));
  398.         } catch (Exception ex) {
  399.             return file;
  400.         }
  401.         }
  402.     }
  403.     return null;
  404.     }
  405.  
  406.     /**
  407.      * Parse the PAUSES parameter.  It looks like
  408.      * 1000|500|||750, etc., where each item corresponds to a
  409.      * source image.  Empty items mean that the corresponding image
  410.      * has no special duration, and should use the global one.
  411.      *
  412.      * @return a Hashtable of Integer pauses keyed to Integer
  413.      * frame numbers.
  414.      */
  415.     Hashtable parseDurations(String attr, Vector images) {
  416.     Hashtable result = new Hashtable();
  417.  
  418.     int imageNum = 0;
  419.     int numImages = images.size();
  420.     for (int i = 0; i < attr.length(); ) {
  421.         if (imageNum >= numImages) break;
  422.         
  423.         int next = attr.indexOf('|', i);
  424.         if (next == -1) next = attr.length();
  425.  
  426.         if (i != next - 1) {
  427.         int duration = Integer.parseInt(attr.substring(i, next));
  428.         result.put(new Integer(imageNum), new Integer(duration));
  429.         } else {
  430.         result.put(new Integer(imageNum),
  431.                new Integer(globalPause));
  432.         }
  433.         i = next + 1;
  434.         imageNum++;
  435.     }
  436.  
  437.     return result;
  438.     }
  439.  
  440.     /**
  441.      * Parse a String of form xxx@yyy and return a Point.
  442.      */
  443.     Point parsePoint(String s) throws ParseException {
  444.     int atPos = s.indexOf('@');
  445.     if (atPos == -1) throw new ParseException("Illegal position: "+s);
  446.     return new Point(Integer.parseInt(s.substring(0, atPos)),
  447.              Integer.parseInt(s.substring(atPos + 1)));
  448.     }
  449.  
  450.  
  451.     /**
  452.      * Parse the POSITIONS parameter.  It looks like
  453.      * 10@30|11@31|||12@20, etc., where each item is an X@Y coordinate
  454.      * corresponding to a source image.  Empty items mean that the
  455.      * corresponding image has the same position as the preceding one.
  456.      *
  457.      * @return a Hashtable of Points keyed to Integer frame numbers.
  458.      */
  459.     Hashtable parsePositions(String param, Vector images)
  460.     throws ParseException {
  461.     Hashtable result = new Hashtable();
  462.  
  463.     int imageNum = 0;
  464.     int numImages = images.size();
  465.     for (int i = 0; i < param.length(); ) {
  466.         if (imageNum >= numImages) break;
  467.         
  468.         int next = param.indexOf('|', i);
  469.         if (next == -1) next = param.length();
  470.  
  471.         if (i != next) {
  472.         result.put(new Integer(imageNum),
  473.                parsePoint(param.substring(i, next)));
  474.         }
  475.         i = next + 1;
  476.         imageNum++;
  477.     }
  478.  
  479.     return result;
  480.     }
  481.     
  482.     /**
  483.      * Get the dimensions of an image.
  484.      * @return the image's dimensions.
  485.      */
  486.     Dimension getImageDimensions(Image im) {
  487.     return new Dimension(im.getWidth(null), im.getHeight(null));
  488.     }
  489.  
  490.     /**
  491.      * Substitute an integer some number of times in a string, subject to
  492.      * parameter strings embedded in the string.
  493.      * Parameter strings:
  494.      *   %N - substitute the integer as is, with no padding.
  495.      *   %<digit>, for example %5 - substitute the integer left-padded with
  496.      *        zeros to <digits> digits wide.
  497.      *   %% - substitute a '%' here.
  498.      * @param inStr the String to substitute within
  499.      * @param theInt the int to substitute.
  500.      */
  501.     String doSubst(String inStr, int theInt) {
  502.     String padStr = "0000000000";
  503.     int length = inStr.length();
  504.     StringBuffer result = new StringBuffer(length);
  505.     
  506.     for (int i = 0; i < length;) {
  507.         char ch = inStr.charAt(i);
  508.         if (ch == '%') {
  509.         i++;
  510.         if (i == length) {
  511.             result.append(ch);
  512.         } else {
  513.             ch = inStr.charAt(i);
  514.             if (ch == 'N') {
  515.             // just stick in the number, unmolested
  516.             result.append(theInt+"");
  517.             i++;
  518.             } else {
  519.             int pad;
  520.             if ((pad = Character.digit(ch, 10)) != -1) {
  521.                 // we've got a width value
  522.                 String numStr = theInt+"";
  523.                 String scr = padStr+numStr;
  524.                 result.append(scr.substring(scr.length() - pad));
  525.                 i++;
  526.             } else {
  527.                 result.append(ch);
  528.                 i++;
  529.             }
  530.             }
  531.         }
  532.         } else {
  533.         result.append(ch);
  534.         i++;
  535.         }
  536.     }
  537.     return result.toString();
  538.     }    
  539.  
  540.     /**
  541.      * Stuff a range of image names into a Vector.
  542.      * @return a Vector of image URLs.
  543.      */
  544.     Vector prepareImageRange(int startImage, int endImage, String pattern)
  545.     throws MalformedURLException {
  546.     Vector result = new Vector(Math.abs(endImage - startImage) + 1);
  547.     if (pattern == null) {
  548.         pattern = "T%N.gif";
  549.     }
  550.     if (startImage > endImage) {
  551.         for (int i = startImage; i >= endImage; i--) {
  552.         result.addElement(new URL(imageSource, doSubst(pattern, i)));
  553.         }
  554.     } else {
  555.         for (int i = startImage; i <= endImage; i++) {
  556.         result.addElement(new URL(imageSource, doSubst(pattern, i)));
  557.         }
  558.     }
  559.     return result;
  560.     }
  561.  
  562.     
  563.     /**
  564.      * Initialize the applet.  Get parameters.
  565.      */
  566.     public void init() {
  567.  
  568.     tracker = new MediaTracker(this);
  569.     
  570.     try {
  571.         String param = getParameter("IMAGESOURCE");    
  572.         imageSource = (param == null) ? getDocumentBase() : new URL(getDocumentBase(), param + "/");
  573.     
  574.         param = getParameter("PAUSE");
  575.         globalPause =
  576.         (param != null) ? Integer.parseInt(param) : defaultPause;
  577.  
  578.         param = getParameter("REPEAT");
  579.         repeat = (param == null) ? true : (param.equalsIgnoreCase("yes") ||
  580.                            param.equalsIgnoreCase("true"));
  581.  
  582.         int startImage = 1;
  583.         int endImage = 1;
  584.         param = getParameter("ENDIMAGE");
  585.         if (param != null) {
  586.         endImage = Integer.parseInt(param);
  587.         param = getParameter("STARTIMAGE");
  588.         if (param != null) {
  589.             startImage = Integer.parseInt(param);
  590.         }
  591.         param = getParameter("NAMEPATTERN");
  592.         images = prepareImageRange(startImage, endImage, param);
  593.         } else {
  594.         param = getParameter("STARTIMAGE");
  595.         if (param != null) {
  596.             startImage = Integer.parseInt(param);
  597.             param = getParameter("NAMEPATTERN");
  598.             images = prepareImageRange(startImage, endImage, param);
  599.         } else {
  600.             param = getParameter("IMAGES");
  601.             if (param == null) {
  602.             showStatus("No legal IMAGES, STARTIMAGE, or ENDIMAGE "+
  603.                    "specified.");
  604.             return;
  605.             } else {
  606.             images = parseImages(param);
  607.             }
  608.         }
  609.         }
  610.  
  611.         param = getParameter("BACKGROUND");
  612.         if (param != null) {
  613.         backgroundImageURL = new URL(imageSource, param);
  614.         }
  615.  
  616.         param = getParameter("STARTUP");
  617.         if (param != null) {
  618.         startUpImageURL = new URL(imageSource, param);
  619.         }
  620.  
  621.         param = getParameter("SOUNDSOURCE");
  622.         soundSource = (param == null) ? imageSource : new URL(getDocumentBase(), param + "/");
  623.     
  624.         param = getParameter("SOUNDS");
  625.         if (param != null) {
  626.         sounds = parseSounds(param, images);
  627.         }
  628.  
  629.         param = getParameter("PAUSES");
  630.         if (param != null) {
  631.         durations = parseDurations(param, images);
  632.         }
  633.  
  634.         param = getParameter("POSITIONS");
  635.         if (param != null) {
  636.         positions = parsePositions(param, images);
  637.         }
  638.  
  639.         param = getParameter("SOUNDTRACK");
  640.         if (param != null) {
  641.         soundtrackURL = new URL(soundSource, param);
  642.         }
  643.     } catch (MalformedURLException e) {
  644.         showParseError(e);
  645.     } catch (ParseException e) {
  646.         showParseError(e);
  647.     }
  648.     
  649.  
  650.  
  651.     setFrameNum(0);
  652.     }
  653.  
  654.     void tellLoadingMsg(String file, String fileType) {
  655.     showStatus("Animator: loading "+fileType+" "+file);
  656.     }
  657.  
  658.     void tellLoadingMsg(URL url, String fileType) {
  659.     tellLoadingMsg(url.toExternalForm(), fileType);
  660.     }
  661.  
  662.     void clearLoadingMessage() {
  663.     showStatus("");
  664.     }
  665.     
  666.     void loadError(String fileName, String fileType) {
  667.     String errorMsg = "Animator: Couldn't load "+fileType+" "+
  668.         fileName;
  669.     showStatus(errorMsg);
  670.     System.err.println(errorMsg);
  671.     error = true;
  672.     repaint();
  673.     }
  674.  
  675.     void loadError(URL badURL, String fileType) {
  676.     loadError(badURL.toExternalForm(), fileType);
  677.     }
  678.  
  679.     void showParseError(Exception e) {
  680.     String errorMsg = "Animator: Parse error: "+e;
  681.     showStatus(errorMsg);
  682.     System.err.println(errorMsg);
  683.     error = true;
  684.     repaint();
  685.     }
  686.  
  687.     void startPlaying() {
  688.     if (soundtrack != null) {
  689.         soundtrack.loop();
  690.     }
  691.     }
  692.  
  693.     void stopPlaying() {
  694.     if (soundtrack != null) {
  695.         soundtrack.stop();
  696.     }
  697.     }
  698.  
  699.     /**
  700.      * Run the animation. This method is called by class Thread.
  701.      * @see java.lang.Thread
  702.      */
  703.     public void run() {
  704.     Thread me = Thread.currentThread();
  705.     URL badURL;
  706.     
  707.     me.setPriority(Thread.MIN_PRIORITY);
  708.  
  709.     if (! loaded) {
  710.         try {
  711.         // ... to do a bunch of loading.
  712.         if (startUpImageURL != null) {
  713.             tellLoadingMsg(startUpImageURL, imageLabel);
  714.             startUpImage = getImage(startUpImageURL);
  715.             tracker.addImage(startUpImage, STARTUP_ID);
  716.             tracker.waitForID(STARTUP_ID);
  717.             if (tracker.isErrorID(STARTUP_ID)) {
  718.             loadError(startUpImageURL, "start-up image");
  719.             }
  720.             Dimension size = getImageDimensions(startUpImage);
  721.             resize(size.width, size.height);
  722.             repaint();
  723.         }
  724.         
  725.         if (backgroundImageURL != null) {
  726.             tellLoadingMsg(backgroundImageURL, imageLabel);
  727.             backgroundImage = getImage(backgroundImageURL);
  728.             tracker.addImage(backgroundImage, BACKGROUND_ID);
  729.             tracker.waitForID(BACKGROUND_ID);
  730.             if (tracker.isErrorID(BACKGROUND_ID)) {
  731.             loadError(backgroundImageURL, "background image");
  732.             }
  733.             updateMaxDims(getImageDimensions(backgroundImage));
  734.             repaint();
  735.         }
  736.  
  737.         // Fetch the animation frames
  738.         if (!fetchImages(images)) {
  739.             // Need to add method to MediaTracker to return
  740.             // files that caused errors during loading.
  741.             loadError("an image", imageLabel);
  742.             return;
  743.         }
  744.  
  745.         if (soundtrackURL != null && soundtrack == null) {
  746.             tellLoadingMsg(soundtrackURL, imageLabel);
  747.             soundtrack = getAudioClip(soundtrackURL);
  748.             if (soundtrack == null) {
  749.             loadError(soundtrackURL, "soundtrack");
  750.             return;
  751.             }
  752.         }
  753.  
  754.         if (sounds != null) {
  755.             badURL = fetchSounds(sounds);
  756.             if (badURL != null) {
  757.             loadError(badURL, soundLabel);
  758.             return;
  759.             }
  760.         }
  761.  
  762.         clearLoadingMessage();
  763.  
  764.         offScrImage = createImage(maxWidth, maxHeight);
  765.         offScrGC = offScrImage.getGraphics();
  766.         offScrGC.setColor(Color.lightGray);
  767.  
  768.         resize(maxWidth, maxHeight);
  769.         loaded = true;
  770.         error = false;
  771.         } catch (Exception e) {
  772.         error = true;
  773.         e.printStackTrace();
  774.         }
  775.     }
  776.  
  777.     if (userPause) {
  778.         return;
  779.     }
  780.  
  781.     if (repeat || frameNum < images.size()) {
  782.         startPlaying();
  783.     }
  784.  
  785.     try {
  786.         if (images.size() > 1) {
  787.         while (maxWidth > 0 && maxHeight > 0 && engine == me) {
  788.             if (frameNum >= images.size()) {
  789.             if (!repeat) {
  790.                 return;
  791.             }
  792.             setFrameNum(0);
  793.             }
  794.             repaint();
  795.  
  796.             if (sounds != null) {
  797.             AudioClip clip =
  798.                 (AudioClip)sounds.get(frameNumKey);
  799.             if (clip != null) {
  800.                 clip.play();
  801.             }
  802.             }
  803.  
  804.             try {
  805.             Integer pause = null;
  806.             if (durations != null) {
  807.                 pause = (Integer)durations.get(frameNumKey);
  808.             }
  809.             if (pause == null) {
  810.                 Thread.sleep(globalPause);
  811.             } else {
  812.                 Thread.sleep(pause.intValue());
  813.             }
  814.             } catch (InterruptedException e) {
  815.             // Should we do anything?
  816.             }
  817.             setFrameNum(frameNum+1);
  818.         }
  819.         }
  820.     } finally {
  821.         stopPlaying();
  822.     }
  823.     }
  824.  
  825.     /**
  826.      * No need to clear anything; just paint.
  827.      */
  828.     public void update(Graphics g) {
  829.     paint(g);
  830.     }
  831.  
  832.     /**
  833.      * Paint the current frame.
  834.      */
  835.     public void paint(Graphics g) {
  836.     if (error || !loaded) {
  837.         if (startUpImage != null) {
  838.         if (tracker.checkID(STARTUP_ID)) {
  839.             g.drawImage(startUpImage, 0, 0, this);
  840.         }
  841.         } else {
  842.         if (backgroundImage != null) {
  843.             if (tracker.checkID(BACKGROUND_ID)) {
  844.             g.drawImage(backgroundImage, 0, 0, this);
  845.             }
  846.         } else {
  847.             g.clearRect(0, 0, maxWidth, maxHeight);
  848.         }
  849.         }
  850.     } else {
  851.         if ((images != null) && (images.size() > 0)) {
  852.         if (frameNum < images.size()) {
  853.             if (backgroundImage == null) {
  854.             offScrGC.fillRect(0, 0, maxWidth, maxHeight);
  855.             } else {
  856.             offScrGC.drawImage(backgroundImage, 0, 0, this);
  857.             }
  858.  
  859.             Image image = (Image)images.elementAt(frameNum);
  860.             Point pos = null;
  861.             if (positions != null) {
  862.             pos = (Point)positions.get(frameNumKey);
  863.             }
  864.             if (pos != null) {
  865.             xPos = pos.x;
  866.             yPos = pos.y;
  867.             }
  868.             offScrGC.drawImage(image, xPos, yPos, this);
  869.             g.drawImage(offScrImage, 0, 0, this);
  870.         } else {
  871.             // no more animation, but need to draw something
  872.             dbg("No more animation; drawing last image.");
  873.             if (backgroundImage == null) {
  874.             g.fillRect(0, 0, maxWidth, maxHeight);
  875.             } else {
  876.             g.drawImage(backgroundImage, 0, 0, this);
  877.             }
  878.             g.drawImage((Image)images.lastElement(), 0, 0, this);
  879.         }
  880.         }
  881.     }
  882.     }
  883.  
  884.     /**
  885.      * Start the applet by forking an animation thread.
  886.      */
  887.     public void start() {
  888.     if (engine == null) {
  889.         engine = new Thread(this);
  890.         engine.start();
  891.     }
  892.     }
  893.  
  894.     /**
  895.      * Stop the insanity, um, applet.
  896.      */
  897.     public void stop() {
  898.     if (engine != null && engine.isAlive()) {
  899.         engine.stop();
  900.     }
  901.     engine = null;
  902.     }
  903.  
  904.     /**
  905.      * Pause the thread when the user clicks the mouse in the applet.
  906.      * If the thread has stopped (as in a non-repeat performance),
  907.      * restart it.
  908.      */
  909.     public boolean handleEvent(Event evt) {
  910.     if (evt.id == Event.MOUSE_DOWN) {
  911.         if (loaded) {
  912.         if (engine != null && engine.isAlive()) {
  913.             if (userPause) {
  914.             engine.resume();
  915.             startPlaying();
  916.             } else {
  917.             engine.suspend();
  918.             stopPlaying();
  919.             }
  920.             userPause = !userPause;
  921.         } else {
  922.             userPause = false;
  923.             setFrameNum(0);
  924.             engine = new Thread(this);
  925.             engine.start();
  926.         }
  927.         }
  928.         return true;
  929.     } else {        
  930.         return super.handleEvent(evt);
  931.     }
  932.     }
  933.     
  934. }
  935.  
  936.  
  937. class ParseException extends Exception {
  938.     ParseException(String s) {
  939.     super(s);
  940.     }
  941. }
  942.  
  943. class ImageNotFoundException extends Exception {
  944.     ImageNotFoundException(ImageProducer source) {
  945.     super(source+"");
  946.     }
  947. }
  948.  
  949.